package com.nxy.cpfsfe.util.sftp;

import com.adtec.starring.exception.BaseException;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.SftpException;
import com.nxy.cpfsfe.enumtype.RespCd;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.List;
import java.util.Vector;

/**
 * 
 * SFTP方式文件传输
 *
 */
public class SFTPHandler {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(SFTPHandler.class);

    private String REMOTE_ENCODE;
    private String LOCAL_ENCODE;

    /**
     * 传输渠道
     */
    private ChannelSftp sftpChannel;

    SFTPHandler(ChannelSftp sftpChannel,String remoteEncode,String localEncode) {
        this.sftpChannel = sftpChannel;
        this.REMOTE_ENCODE = remoteEncode;
        this.LOCAL_ENCODE = localEncode;
    }

    /**
     * 对路径进行初始化
     *
     * @param path 原始路径
     * @return 初始化后的路径
     */
    String adjustPath(String path) {
        path = path.replace('\\', '/');
        path = path.replaceAll("//", "/");
        return path;
    }

    /**
     * 判断文件是否存在，资源不存在则抛异常。
     *
     * @param remotePath 服务器目录
     * @return 是否存在目录
     * @throws Exception 抛出异常
     */
    @SuppressWarnings("rawtypes")
    private boolean isExist(String remotePath) throws Exception {

        LOGGER.info("远程目录:{}", remotePath);

        boolean exist = false;
        try {
            Vector entries = sftpChannel.ls(remotePath);
            if (entries != null && entries.size() > 0)
                exist = true;
        } catch (SftpException e) {
            String errMsg = String.format("%d,访问远程资源[%s]失败,%s!", e.id, remotePath, e.getMessage());
            LOGGER.error(errMsg);
            throw new BaseException(RespCd.NCPS9400, errMsg);
        }

        return exist;
    }

    /**
     * 判断文件是否存在，不抛异常
     * @param remotePath 远程目录
     * @return 返回远程目录是否存在
     */
    private boolean isExistQuiet(String remotePath) {
        boolean isExist;
        try {
            isExist = isExist(remotePath);
        } catch (Exception e) {
            isExist = false;
        }
        return isExist;
    }

    /**
     * 判断是否为目录
     *
     * @param remotePath 远程目录
     * @return 远程目录是否是目录
     * @throws Exception 抛出异常
     */
    @SuppressWarnings("rawtypes")
    private boolean isDirectory(String remotePath) throws Exception {

        boolean isDirectory = true;

        if (isExist(remotePath)) {
            remotePath = new String(remotePath.getBytes(LOCAL_ENCODE), REMOTE_ENCODE);
            try {
                Vector entries = sftpChannel.ls(remotePath);
                isDirectory = entries != null && entries.size() >= 2;
            } catch (Exception e) {
                isDirectory = false;
            }
        }

        return isDirectory;
    }

    /**
     * 判断是否为文件
     *
     * @param remotePath 远程目录
     * @return 返回是否为文件
     * @throws Exception 抛出异常
     */
    @SuppressWarnings("rawtypes")
    public boolean isFile(String remotePath) throws Exception {

        boolean isFile = false;

        if (isExist(remotePath)) {
            remotePath = new String(remotePath.getBytes(LOCAL_ENCODE), REMOTE_ENCODE);
            try {
                Vector entries = sftpChannel.ls(remotePath);
                if (entries != null && entries.size() == 1) {
                    LsEntry entry = (LsEntry) entries.get(0);
                    if (!entry.getAttrs().isDir() && !entry.getAttrs().isLink())
                        isFile = true;
                }
            } catch (Exception e) {
                LOGGER.error(e.getMessage());
            }
        }

        return isFile;
    }

    /**
     * 创建目录 quiet为true时，不抛异常；quiet为false时，抛异常
     *
     * @param originalRemotePath 原始远程目录
     * @param remotePath 远程目录
     * @param quiet 是否安静模式
     * @return 返回执行结果
     * @throws Exception 抛出异常
     */
    private boolean makeDirectory(String originalRemotePath, String remotePath,boolean quiet) throws Exception {

        boolean makeSuccess = true;
        remotePath = new String(remotePath.getBytes(LOCAL_ENCODE), REMOTE_ENCODE);

        if (remotePath.length() > 0) {
            try {

                int position = remotePath.lastIndexOf('/');
                if (position != -1) {
                    String upper = remotePath.substring(0,
                            remotePath.lastIndexOf('/'));
                    if (upper.length() > 0) {
                        upper = new String(upper.getBytes(REMOTE_ENCODE), LOCAL_ENCODE);
                        makeDirectory(originalRemotePath, upper, quiet);
                    }
                }

                String tmpPath = remotePath;
                tmpPath = new String(tmpPath.getBytes(REMOTE_ENCODE), LOCAL_ENCODE);
                if (!quiet && originalRemotePath.equals(remotePath) && isExistQuiet(tmpPath))
                    throw new Exception("远程资源[" + tmpPath + "]已经存在！");

                sftpChannel.mkdir(remotePath);

            } catch (SftpException e) {
                makeSuccess = false;
            }
        }

        return makeSuccess;
    }

    /**
     * 删除文件及目录
     * @param remotePath 远程目录
     * @throws Exception 抛出异常
     */
    @SuppressWarnings(value={"unchecked", "rawtypes"})
    private boolean deleteFile(String remotePath) throws Exception {

        boolean success = true;

        if (isExist(remotePath)) {
            remotePath = new String(remotePath.getBytes(LOCAL_ENCODE), REMOTE_ENCODE);

            Vector<LsEntry> entries = (Vector<LsEntry>)sftpChannel.ls(remotePath);
            String tmpPath = new String(remotePath.getBytes(REMOTE_ENCODE), LOCAL_ENCODE);
            boolean isDir = isDirectory(tmpPath);
            try {
                if (isDir) {
                    for (LsEntry entry : entries) {
                        String fileName = entry.getFilename();
                        if (!fileName.equals(".") && !fileName.equals("..")) {
                            String tmpFilePath = remotePath + "/" + fileName;
                            tmpPath = new String(tmpFilePath.getBytes(REMOTE_ENCODE), LOCAL_ENCODE);
                            boolean deleted = deleteFile(tmpPath);
                            if (!deleted) {
                                return false;
                            }
                        }
                    }
                    sftpChannel.rmdir(remotePath);
                } else {
                    sftpChannel.rm(remotePath);
                }
            } catch (Exception e) {
                success = false;
            }
        }

        return success;
    }

    /**
     * 重命名文件及目录
     * @param remotePath 远程原目录
     * @param newPath 远程目标目录
     * @throws Exception 抛出异常
     */
    public boolean renameFile(String remotePath, String newPath)
            throws Exception {

        boolean success = true;

        if (isExist(remotePath)) {
            remotePath = new String(remotePath.getBytes(LOCAL_ENCODE), REMOTE_ENCODE);
            if (isExistQuiet(newPath))
                throw new Exception("远程资源[" + newPath + "]已经存在！");

            newPath = new String(newPath.getBytes(LOCAL_ENCODE), REMOTE_ENCODE);
            try {
                sftpChannel.rename(remotePath, newPath);
            } catch (Exception e) {
                success = false;
            }
        } else {
            throw new Exception("远程资源[" + remotePath + "]不存在！");
        }

        return success;
    }

    /**
     * 获取文件大小
     * @param remotePath 远程目录文件
     * @return 返回文件大小
     * @throws Exception 抛出异常
     */
    @SuppressWarnings(value={"unchecked", "rawtypes"})
    private long getFileSize(String remotePath) throws Exception {

        long size = 0;

        if (isExist(remotePath)) {

            remotePath = new String(remotePath.getBytes(LOCAL_ENCODE), REMOTE_ENCODE);
            Vector<LsEntry> entries = sftpChannel.ls(remotePath);

            for (LsEntry entry : entries) {
                String fileName = entry.getFilename();
                if (!".".equals(fileName) && !"..".equals(fileName)) {
                    if (entry.getAttrs().isDir()) {
                        String tmpPath = remotePath + "/" + fileName;
                        tmpPath = new String(tmpPath.getBytes(REMOTE_ENCODE), LOCAL_ENCODE);
                        size += getFileSize(tmpPath);
                    } else if (!entry.getAttrs().isLink()) {
                        size += entry.getAttrs().getSize();
                    }
                }
            }
        }

        return size;
    }

    /**
     * 获取子文件数
     * @param remotePath 远程目录
     * @return 返回文件数
     * @throws Exception 抛出异常
     */
    @SuppressWarnings(value={"unchecked", "rawtypes"})
    private int getFileAmount(String remotePath) throws Exception {

        int amount = 0;

        if (isExist(remotePath)) {
            remotePath = new String(remotePath.getBytes(LOCAL_ENCODE), REMOTE_ENCODE);
            Vector<LsEntry> entries = sftpChannel.ls(remotePath);

            for (LsEntry entry : entries) {
                String fileName = entry.getFilename();
                if (!".".equals(fileName) && !"..".equals(fileName)) {
                    if (entry.getAttrs().isDir()) {
                        amount++;
                        String tmpPath = remotePath + "/" + fileName;
                        tmpPath = new String(tmpPath.getBytes(REMOTE_ENCODE), LOCAL_ENCODE);
                        amount += getFileAmount(tmpPath);
                    } else if (!entry.getAttrs().isLink()) {
                        amount++;
                    }
                }
            }
        }

        return amount;
    }

    /**
     * 文件上传
     *
     * @param localPath 本地目录
     * @param remotePath 远程目录
     * @return 上传字节数
     * @throws Exception 抛出异常
     */
    int upload(String localPath, String remotePath) throws Exception {

        int count = 0;
        File localFile = new File(localPath);
        if (!localFile.exists())
            throw new Exception("上传文件不存在： " + localPath);

        if (localFile.isDirectory()) {

            String destPath = remotePath;
            if (destPath.endsWith("/")) {
                destPath += localFile.getName();
            } else {
                destPath += "/" + localFile.getName();
            }

            if (!isExistQuiet(destPath)) {
                boolean success = makeDirectory(destPath, destPath, true);
                if (!success)
                    throw new Exception("创建目录失败：" + destPath);
            } else {
                LOGGER.debug("远程文件" + destPath + "已经存在");
            }

            File[] childs = localFile.listFiles();
            if (childs != null && childs.length > 0) {
                for (File child : childs) {
                    String srcPath = child.getCanonicalPath();
                    count += upload(srcPath, destPath);
                }
            }

        } else {
            FileInputStream fis = null;
            try {
                fis = new FileInputStream(localFile);
                String remoteFilePath = remotePath;
                
                if (!isExistQuiet(remoteFilePath)) {
                    boolean success = makeDirectory(remoteFilePath, remoteFilePath, true);
                    if (!success)
                        throw new Exception("创建目录失败：" + remoteFilePath);
                } else {
                    LOGGER.debug("远程文件" + remoteFilePath + "已经存在");
                }
                
                if (remoteFilePath.endsWith("/")) {
                    remoteFilePath += localFile.getName();
                } else {
                    remoteFilePath += "/";
                    remoteFilePath += localFile.getName();
                }

                sftpChannel.put(fis, new String(remoteFilePath.getBytes(LOCAL_ENCODE), REMOTE_ENCODE));

                LOGGER.debug("上传文件{}到{}成功,文件大小为：{}bytes",
                        localPath,
                        new String(remoteFilePath.getBytes(REMOTE_ENCODE), LOCAL_ENCODE), localFile.length());
                count++;
            } catch (Exception e) {
                throw new Exception("上传文件：" + localPath + " 失败:"
                        + e.getMessage());
            } finally {
                if (fis != null)
                    fis.close();
            }
        }
        return count;
    }

    /**
     * 文件下载
     * @param localPath 本地目录
     * @param remotePath 远程目录
     * @return 下载路径
     * @throws Exception 抛出异常
     */
    @SuppressWarnings(value={"unchecked", "rawtypes"})
    int downloadPath(String localPath, String remotePath) throws Exception {
        int count = 0;

        remotePath = new String(remotePath.getBytes(LOCAL_ENCODE), REMOTE_ENCODE);
        if (isExist(remotePath)) {

            Vector<LsEntry> remoteFiles;
            boolean isDir = false;
            try {
                remoteFiles = (Vector<LsEntry>)sftpChannel.ls(remotePath);
                if (remoteFiles != null && remoteFiles.size() >= 2)
                    isDir = true;
            } catch (SftpException e) {
                throw new Exception("下载文件,获取SFTP端文件列表时发生异常");
            }

            String dirName = remotePath.substring(remotePath.lastIndexOf('/') + 1);
            if (isDir) {
                String destPath = localPath + "/" + dirName;
                boolean succeed = mkLocalDirs(destPath);
                if (!succeed) {
                    throw new Exception("创建本地目录失败:" + destPath);
                }
                for (LsEntry entry : remoteFiles) {
                    String fileName = entry.getFilename();
                    String tmpFileName = new String(fileName.getBytes(REMOTE_ENCODE), LOCAL_ENCODE);
                    if (!fileName.equals(".") && !fileName.equals("..")) {
                        String srcPath = remotePath + "/" + tmpFileName;
                        count += downloadPath(destPath, srcPath);
                    }
                }
            } else {
                File localFile = new File(localPath);
                if (localFile.exists()) {
                    if (!localFile.isDirectory())
                        throw new Exception("本地路径错误[" + localPath + "]，必须为目录");
                } else {
                    boolean succeed = localFile.mkdirs();
                    if (!succeed) {
                        throw new Exception("创建本地路径失败:" + localFile);
                    }
                }

                File file = new File(localPath + "/" + dirName);
                FileOutputStream fos = new FileOutputStream(file);
                try {
                    sftpChannel.get(remotePath, fos);
                    LOGGER.info("下载文件[{}]到[{}]成功,文件大小为[{}]bytes",
                            remotePath, file.getCanonicalPath(), file.length());
                } catch (Exception e) {
                    throw new Exception("下载文件:" + remotePath + "失败");
                } finally {
                    fos.close();
                }
                count++;
            }
        }
        return count;
    }

    void downloadFileList(String localPath, String remotePath,String[] fileArray, List<String> fileList) throws Exception {

        for (String fileName : fileArray) {

            String remoteFileName = remotePath + "/" + fileName;
            String localFileName = localPath + "/" + fileName;

            localFileName = adjustPath(localFileName);
            remoteFileName = adjustPath(remoteFileName);
            remoteFileName = new String(remoteFileName.getBytes(LOCAL_ENCODE), REMOTE_ENCODE);

            downloadFile(localFileName, remoteFileName);

            fileList.add(fileName);
        }
    }

    @SuppressWarnings("rawtypes")
    private void downloadFile(String localFile, String remoteFile) throws Exception {

        FileOutputStream fos = null;
        Vector remoteFiles;

        try {
            remoteFile = new String(remoteFile.getBytes(LOCAL_ENCODE), REMOTE_ENCODE);

            if (!isExist(remoteFile)) {
                String errMsg = String.format("SFTP-远程文件[%s]不存在!", remoteFile);
                LOGGER.error(errMsg);
                throw new BaseException(RespCd.NCPS9400, errMsg);
            }

            remoteFiles = sftpChannel.ls(remoteFile);
            if (remoteFiles != null && remoteFiles.size() >= 2) {
                String errMsg = String.format("SFTP-远程文件[%s]为目录,下载文件失败.", remoteFile);
                LOGGER.error(errMsg);
                throw new BaseException(RespCd.NCPS9400, errMsg);
            }

            String localPathName = localFile.substring(0, localFile.lastIndexOf("/"));
            File localPath = new File(localPathName);
            if (localPath.exists()) {
                if (!localPath.isDirectory()) {
                    String errMsg = String.format("SFTP-本地路径[%s]不是目录.", localPath);
                    LOGGER.error(errMsg);
                    throw new BaseException(RespCd.NCPS9400, errMsg);
                }
            } else {
                boolean succeed;
                int count = 0;
                do {
                    succeed = localPath.mkdirs();
                    count++;
                } while (!succeed && count < 10);

                if (!succeed) {
                    if (!localPath.exists()) {
                        String errMsg = String.format("SFTP-本地路径[%s]创建失败.", localPath);
                        LOGGER.error(errMsg);
                        throw new BaseException(RespCd.NCPS9400, errMsg);
                    }
                }
            }

            File file = new File(localFile);
            fos = new FileOutputStream(file);
            sftpChannel.get(remoteFile, fos);

            LOGGER.info("下载文件[{}]到[{}]成功,文件大小为[{}]bytes",
                    remoteFile, file.getCanonicalPath(), file.length());

        } catch (Exception e) {

            LOGGER.error("下载文件{}失败.", remoteFile);
            throw e;

        } finally {
            if (fos != null)
                fos.close();
        }
    }

    /**
     * 获取文件列表
     * @param remotePath 远程路径
     * @throws Exception 抛出异常
     */
    @SuppressWarnings(value={"unchecked", "rawtypes"})
    void getList(String remotePath, String matchPattern, List<String> fileList) throws Exception {

        remotePath = new String(remotePath.getBytes(LOCAL_ENCODE), REMOTE_ENCODE);
        if (isExist(remotePath)) {

            Vector<LsEntry> remoteFiles;
            boolean isDir = false;
            try {
                remoteFiles = (Vector<LsEntry>)sftpChannel.ls(remotePath);
                if (remoteFiles != null && remoteFiles.size() >= 2)
                    isDir = true;
            } catch (SftpException e) {
                String errMsg = String.format("%d,远程资源[%s]不存在,%s!", e.id, remotePath, e.getMessage());
                LOGGER.error(errMsg);
                throw new BaseException(RespCd.NCPS9400, errMsg);
            }

            if (!isDir) {
                String errMsg = String.format("远程资源[%s]不是目录!", remotePath);
                LOGGER.error(errMsg);
                throw new BaseException(RespCd.NCPS9400, errMsg);
            }

            for (LsEntry remoteFile : remoteFiles) {
                String fileName = remoteFile.getFilename();
                fileName = new String(fileName.getBytes(REMOTE_ENCODE), LOCAL_ENCODE);
                if (match(fileName, matchPattern))
                    fileList.add(fileName);
            }
        }
    }

    private boolean match(String fileName, String matchPattern) {

        if (matchPattern == null || matchPattern.equals(""))
            return false;

        int index = matchPattern.indexOf("*");
        if (index < 0) {
            return matchPattern.equals(fileName);
        }

        String bgnWith = null;
        String endWith = null;

        if (matchPattern.equals("*"))
            return true;

        if (index == 0)
            endWith = matchPattern.substring(1);
        else if (index + 1 == matchPattern.length())
            bgnWith = matchPattern.substring(0, index);
        else {
            bgnWith = matchPattern.substring(0, index);
            endWith = matchPattern.substring(index + 1);
        }

        if (bgnWith != null) {
            if (!fileName.startsWith(bgnWith))
                return false;
        }

        if (endWith != null) {
            return fileName.endsWith(endWith);
        }

        return true;
    }

    /**
     * 创建本地目录
     * @param path 本地目录
     */
    private boolean mkLocalDirs(String path) {

        File file = new File(path);
        if (file.isFile())
            return false;

        if (file.isDirectory())
            return false;

        return file.mkdirs();
    }

}
